package cz.ctu.guiproject.client.xml;

import java.io.IOException;
import java.io.StringReader;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import android.util.Log;
import cz.ctu.guiproject.client.dtos.ResponseState;
import cz.ctu.guiproject.client.messaging.DisconnectResponse;
import cz.ctu.guiproject.client.messaging.ForceUIUpdate;
import cz.ctu.guiproject.client.messaging.InitResponse;
import cz.ctu.guiproject.client.resources.InstalledComponent;
import static cz.ctu.guiproject.client.resources.constants.Constants.*;
import static cz.ctu.guiproject.client.resources.constants.Logger.*;

public class XmlDecodeFactory {

	public static String[] decodeConfig(String xml) {
		final SAXParserFactory factory = SAXParserFactory.newInstance();
		final ConfigHandler handler = new ConfigHandler();
		try {
			final SAXParser saxParser = factory.newSAXParser();
			saxParser.parse(new InputSource(new StringReader(xml)), handler);
		} catch (ParserConfigurationException e) {
			Log.e(TAG, e.getMessage());
		} catch (SAXException e) {
			Log.e(TAG, e.getMessage());
		} catch (IOException e) {
			Log.e(TAG, e.getMessage());
		}
		return handler.getConfig();
	}

	private static class ConfigHandler extends DefaultHandler {

		private boolean inIp;
		private boolean inPort;

		private String[] config;

		ConfigHandler() {
			config = new String[2];
		}

		String[] getConfig() {
			return config;
		}

		@Override
		public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
			inIp = qName.equals(SERVER_IP);
			inPort = qName.equals(SERVER_PORT);
		}

		@Override
		public void characters(char[] ch, int start, int length) throws SAXException {
			final String chars = new String(ch).substring(start, start + length);
			if (inIp) {
				config[0] = chars;
			} else if (inPort) {
				config[1] = chars;
			}
		}
	}

	public static ForceUIUpdate decodeForceUIUpdate(String xml) {
		final SAXParserFactory factory = SAXParserFactory.newInstance();
		final ForceUIUpdateHandler handler = new ForceUIUpdateHandler();
		try {
			final SAXParser saxParser = factory.newSAXParser();
			saxParser.parse(new InputSource(new StringReader(xml)), handler);
		} catch (ParserConfigurationException e) {
			Log.e(TAG, e.getMessage());
		} catch (SAXException e) {
			Log.e(TAG, e.getMessage());
		} catch (IOException e) {
			Log.e(TAG, e.getMessage());
		}
		return handler.getForceUIUpdate();
	}

	private static class ForceUIUpdateHandler extends DefaultHandler {

		private boolean inViewId;
		private boolean inAttrName;
		private boolean inAttrValue;

		private static final String VIEW_ID = "viewId";
		private static final String ATTRIBUTE_NAME = "attrName";
		private static final String ATTRIBUTE_VALUE = "attrValue";

		private ForceUIUpdate forceUIUpdate;

		ForceUIUpdateHandler() {
			forceUIUpdate = new ForceUIUpdate();
		}

		ForceUIUpdate getForceUIUpdate() {
			return forceUIUpdate;
		}

		@Override
		public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
			inViewId = qName.equals(VIEW_ID);
			inAttrName = qName.equals(ATTRIBUTE_NAME);
			inAttrValue = qName.equals(ATTRIBUTE_VALUE);
		}

		@Override
		public void characters(char[] ch, int start, int length) throws SAXException {
			String chars = new String(ch).substring(start, start + length);
			if (inViewId) {
				forceUIUpdate.setViewId(chars);
			} else if (inAttrName) {
				forceUIUpdate.setAttrName(chars);
			} else if (inAttrValue) {
				forceUIUpdate.setAttrValue(chars);
			}
		}
	}

	public static DisconnectResponse decodeDisconnectResponse(String xml) {

		final SAXParserFactory factory = SAXParserFactory.newInstance();
		final DisconnectResponseHandler handler = new DisconnectResponseHandler();
		try {
			final SAXParser saxParser = factory.newSAXParser();
			saxParser.parse(new InputSource(new StringReader(xml)), handler);
		} catch (ParserConfigurationException e) {
			Log.e(TAG, e.getMessage());
		} catch (SAXException e) {
			Log.e(TAG, e.getMessage());
		} catch (IOException e) {
			Log.e(TAG, e.getMessage());
		}
		return handler.getDisconnectResponse();
	}

	private static class DisconnectResponseHandler extends DefaultHandler {

		private boolean inResponseState;

		private DisconnectResponse disconnectResponse;

		DisconnectResponseHandler() {
			Log.i(TAG, "DisconnectResponse constructor called.");
			disconnectResponse = new DisconnectResponse();
		}

		DisconnectResponse getDisconnectResponse() {
			return disconnectResponse;
		}

		@Override
		public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
			inResponseState = qName.equals(RESPONSE_STATE);
		}

		@Override
		public void characters(char[] ch, int start, int length) throws SAXException {
			final String chars = new String(ch).substring(start, start + length);
			if (inResponseState) {
				if (chars.equals(ACCEPTED)) {
					disconnectResponse.setResponse(ResponseState.ACCEPTED);
				} else {
					disconnectResponse.setResponse(ResponseState.REJECTED);
				}
			}
		}
	}

	public static InitResponse decodeInitClient(String xml) {

		final SAXParserFactory factory = SAXParserFactory.newInstance();
		final InitResponseHandler handler = new InitResponseHandler();
		try {
			final SAXParser saxParser = factory.newSAXParser();
			saxParser.parse(new InputSource(new StringReader(xml)), handler);
		} catch (ParserConfigurationException e) {
			Log.e(TAG, e.getMessage());
		} catch (SAXException e) {
			Log.e(TAG, e.getMessage());
		} catch (IOException e) {
			Log.e(TAG, e.getMessage());
		}
		return handler.getInitResponse();
	}

	private static class InitResponseHandler extends DefaultHandler {

		private final InitResponse initResponse;
		private boolean inResponseState;
		private boolean inJarUrls;

		InitResponseHandler() {
			Log.i(TAG, "InitResponse constructor called.");
			initResponse = new InitResponse();
		}

		InitResponse getInitResponse() {
			return initResponse;
		}

		@Override
		public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
			inResponseState = qName.equals(RESPONSE_STATE);
			inJarUrls = qName.equals(JAR_URLS);
		}

		@Override
		public void characters(char[] ch, int start, int length) throws SAXException {
			String chars = new String(ch).substring(start, start + length);
			if (inResponseState) {
				if (chars.equals(ACCEPTED)) {
					initResponse.setResponse(ResponseState.ACCEPTED);
				} else {
					initResponse.setResponse(ResponseState.REJECTED);
				}
			} else if (inJarUrls) {
				final StringTokenizer st = new StringTokenizer(chars, ",");
				final List<String> list = new LinkedList<String>();
				while (st.hasMoreTokens()) {
					list.add(st.nextToken().trim());
				}
				final String[] res = new String[list.size()];
				list.toArray(res);
				initResponse.setJarUrls(res);
			}
		}
	}

	public static List<InstalledComponent> decodeInstalledComponents(String xml) {
		final SAXParserFactory factory = SAXParserFactory.newInstance();
		final InstalledComponentsHandler handler = new InstalledComponentsHandler();
		try {
			final SAXParser saxParser = factory.newSAXParser();
			saxParser.parse(new InputSource(new StringReader(xml)), handler);
		} catch (ParserConfigurationException e) {
			Log.e(TAG, e.getMessage());
		} catch (SAXException e) {
			Log.e(TAG, e.getMessage());
		} catch (IOException e) {
			Log.e(TAG, e.getMessage());
		}
		return handler.getInstalledComponents();
	}

	private static class InstalledComponentsHandler extends DefaultHandler {
		private boolean inComponentVersion;
		private boolean inComponentName;

		private List<InstalledComponent> installedComponents;
		private InstalledComponent currentComponent;

		InstalledComponentsHandler() {
			installedComponents = new LinkedList<InstalledComponent>();
		}

		List<InstalledComponent> getInstalledComponents() {
			return installedComponents;
		}

		@Override
		public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
			inComponentVersion = qName.equals(SERVER_INSTALLED_COMPONENT_VERSION);
			inComponentName = qName.equals(SERVER_INSTALLED_COMPONENT_NAME);
			if (qName.equals(SERVER_INSTALLED_COMPONENT)) {
				currentComponent = new InstalledComponent();
			}
		}

		@Override
		public void characters(char[] ch, int start, int length) throws SAXException {
			String chars = new String(ch).substring(start, start + length);
			if (inComponentName) {
				currentComponent.setName(chars);
			} else if (inComponentVersion) {
				try {
					currentComponent.setVersion(Double.parseDouble(chars));
				} catch (NumberFormatException e) {
					Log.e(TAG, e.getMessage());
				}
			}
		}

		@Override
		public void endElement(String uri, String localName, String qName) throws SAXException {
			if (qName.equals(SERVER_INSTALLED_COMPONENT)) {
				installedComponents.add(currentComponent);
			}
		}
	}
}
